package ray;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import ray.Light;
import ray.math.Color;

public class RayTracer {

    public static String testFolderPath;

    public static final double MIN_CONTRIBUTION = 1e-3;

    public static String getTestFolderPath() {
        return testFolderPath;
    }

    /**
     * If filename is a directory, set testFolderPath = fn. And return a list of all .xml files inside the directory
     *
     * @param fileName
     *            Filename or directory
     * @return fn itself in case fn is a file, or all .xml files inside fn
     */
    public static final List<String> getFileLists(String fileName) {
        if (fileName.endsWith("/"))
            fileName.substring(0, fileName.length() - 1);

        File file = new File(fileName);
        List<String> output = new ArrayList<String>();
        if (file.exists()) {
            if (file.isFile()) {
                if (file.getParent() != null) {
                    testFolderPath = file.getParent() + "/";
                } else {
                    testFolderPath = "";
                }
                output.add(fileName);
            } else {
                testFolderPath = fileName + "/";
                for (String fl : file.list()) {
                    if (fl.endsWith(".xml")) {
                        output.add(testFolderPath + fl);
                    }
                }
            }
        } else {
            System.out.println("File not found.");
        }

        return output;
    }

    /**
     * The main method takes all the parameters an assumes they are input files for the ray tracer. It tries to render each one
     * and write it out to a PNG file named <input_file>.png.
     *
     * @param args
     */
    public static final void main(String[] args) {

        Parser parser = new Parser();
        for (String arg : args) {

            List<String> fileLists = getFileLists(arg);
            for (String inputFilename : fileLists) {
                String outputFilename = inputFilename + ".png";

                System.out.println("Processing file: " + inputFilename);

                // Parse the input file
                Scene scene = (Scene) parser.parse(inputFilename, Scene.class);

                // Propagate transformation matrix through the tree hierarchy
                scene.setTransform();

                // Initialize AABB tree
                scene.initializeAABB();

                // Render the scene
                renderImage(scene);

                // Write the image out
                scene.getImage().write(outputFilename);

                System.out.println();
            }
        }
    }

    /**
     * The renderImage method renders the entire scene.
     *
     * @param scene
     *            The scene to be rendered
     */
    public static void renderImage(Scene scene) {

        // Get the output image
        Image image = scene.getImage();
        Camera camera = scene.getCamera();

        // Using image.getWidth() and image.getHeight() sets the camera aspect ratio to match output image

        // Timing counters
        long startTime = System.currentTimeMillis();

        // Do some basic setup
        Calc workspace = new Calc();
        Ray ray = workspace.eyeRay;
        Color pixelColor = workspace.pixelColor;
        Color rayColor = workspace.rayColor;

        int total = image.getHeight() * image.getWidth();
        int counter = 0;
        int lastShownPercent = 0;

        for (int y = 0; y < image.getHeight(); y++) {
            for (int x = 0; x < image.getWidth(); x++) {

                rayColor.scale(0);

                for (int ySample = 0; ySample < scene.getSampleCount(); ySample++) {
                    for (int xSample = 0; xSample < scene.getSampleCount(); xSample++) {

                        double inV = (y + (ySample + .5) / scene.getSampleCount()) / image.getHeight();
                        double inU = (x + (xSample + .5) / scene.getSampleCount()) / image.getWidth();

                        assert 0 <= inU && inU <= 1 : "inU out of bounds: " + inU;
                        assert 0 <= inV && inV <= 1 : "inU out of bounds: " + inU;

                        camera.getRay(ray, inU, inV);

                        Color colorFromRay = new Color();
                        shadeRay(colorFromRay, scene, ray, workspace, scene.getLights(), 1, 1, false);
                        rayColor.add(colorFromRay);
                    }
                }
               
                rayColor.scale(1/Math.pow(scene.getSampleCount(), 2));

                pixelColor.set(rayColor);

                // Gamma correct and clamp pixel values
                pixelColor.gammaCorrect(2.2);
                pixelColor.clamp(0, 1);
                image.setPixelColor(pixelColor, x, y);

                counter++;
                if ((int) (100.0 * counter / total) != lastShownPercent) {
                    lastShownPercent = (int) (100.0 * counter / total);
                    System.out.println(lastShownPercent + "%");
                }
            }
        }

        // Output time
        long totalTime = (System.currentTimeMillis() - startTime);
        System.out.println("Done.  Total rendering time: " + (totalTime / 1000.0) + " seconds");

    }

    /**
     * This method returns the color along a single ray in outColor.
     *
     * @param outColor
     *            output space
     * @param scene
     *            the scene
     * @param ray
     *            the ray to shade
     */
    public static void shadeRay(Color outColor, Scene scene, Ray ray, Calc workspace, ArrayList<Light> lights, int depth,
            double contribution, boolean internal) {
        // TODO(B): Set base case for recursive ray tracing
        if (contribution < MIN_CONTRIBUTION) {
            return;
        }

        // Reset the output color
        outColor.set(0, 0, 0);

        // Rename all the workspace entries to avoid field accesses
        // and a lot of typing "workspace."
        Intersection eyeRecord = workspace.eyeRecord;

        if (!scene.getFirstIntersection(eyeRecord, ray)) {
            return;
        }

         eyeRecord.surface.getShader().shade(outColor, scene, lights, workspace.getToEye(), eyeRecord, depth, contribution,
                internal);
    }

}

